﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SafDocumentor.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Actions.Doc
{
    using System;
    using System.IO;

    using Definitions;

    using Engine;

    using Entities;

    using Ionic.Zip;

    using Templating;

    using Utils;

    using FileInfo=System.IO.FileInfo;

    /// <summary>
    /// This class is responsible for :
    ///  - Producing Macro Templates.
    ///  - Producing HTML Tables for all the Actions and Entities (based on Attributes).
    /// </summary>
    internal class SafDocumentor : ActionBase<SafDocInfo>
    {
        #region Constants and Fields

        private const string FeatureFolder = @"{0}\Features\{1}Feature\";

        private const string FeatureXmlFileName = "feature.xml";

        private const string MacroInstanceFileName = "{0}.macro";

        private const string Macrotemplate = "MacroTemplate";

        private const string MacroTemplateFileName = "{0}.macroTemplate";

        private const string Msbuild = "MSbuild";

        private const string MSBuildFileName = "{0}.proj";

        private const string MsBuildFolder = @"{0}\MsBuild\";

        private const string RunStsAdmBatFileName = "RunStsAdm.bat";

        private const string StsAdmFolder = @"{0}\StsAdm\";

        private const string StsAdmName = "StsAdm";

        #endregion

        #region Enums

        /// <summary>
        /// 
        /// </summary>
        public enum NVelocityTemplateName
        {
            /// <summary>
            /// 
            /// </summary>
            ClassContentsPage,
            /// <summary>
            /// 
            /// </summary>
            ActionIndexPage,
            /// <summary>
            /// 
            /// </summary>
            ActionTemplate,
            /// <summary>
            /// 
            /// </summary>
            ReadMe,
            FeatureInstallTemplate,
            MSBuildInstallTemplate,
            CodePlexWiki
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Executes the Action using the values passed in by the Data Entity
        /// and the <see cref="SafContext"/>.
        /// </summary>
        /// <param name="context">A valid <see cref="SafContext"/> containing properties to run the Action with.</param>
        /// <param name="entity">A Valid <see cref="T"/> containing the parameters with which to run the Action with.</param>
        /// <returns>
        /// A <see cref="ProcessStatus"/> signifying the outcome of processing the Action.
        /// </returns>
        protected override ProcessStatus DoAction(SafContext context, SafDocInfo entity)
        {
            // Deletes, clears and creates the Documentation folder.
            this.EnsureFolder(entity.DocumentationPath);

            // Creates the Macro Templates needed.
            this.DocumentActions(entity, context);

            return ProcessStatus.Success;
        }

        private void ClearFolder(string parentPath)
        {
            string[] alldirs = Directory.GetDirectories(parentPath);
            foreach (string subfolder in alldirs)
            {
                DirectoryInfo info = new DirectoryInfo(subfolder);
                FileHelper.DeleteFileSystemInfo(info);
                //Directory.Delete(subfolder, true);
            }
        }

        private void CopySupportingFiles(
            SafDocInfo entity, SafContext context, SafClassDefinition definition, string docRootFolder)
        {
            string rootDire =
                Directory.GetParent(Directory.GetParent(Environment.CurrentDirectory).ToString()).ToString();

            //string productFolder = Path.Combine(docRootFolder, definition.FullName);
            //string productFolderName = definition.ClassName + "Feature";
            //string subProductFolder = Path.Combine(productFolder, productFolderName);
            //string rootFolder = Path.Combine(Path.Combine(docRootFolder, "Samples"), productFolderName + "s");

            if (definition.SupportFiles != null && definition.SupportFiles.Length > 0)
            {
                foreach (string file in definition.SupportFiles)
                {
                    string newRootDire = Path.Combine(rootDire, definition.AssemblyName);
                    string supportFilePath = Path.Combine(newRootDire, file);

                    if (!File.Exists(supportFilePath))
                    {
                        throw new FileNotFoundException(
                            "Can't find this file : " + supportFilePath + " - " + definition.ClassName);
                    }

                    FileInfo sourceFile = new FileInfo(supportFilePath);

                    string supportFileName = Path.GetFileName(supportFilePath);
                    sourceFile.CopyTo(Path.Combine(this.GetFeatureFolder(docRootFolder, definition), supportFileName), true);
                    sourceFile.CopyTo(Path.Combine(this.GetMSBuildFolder(docRootFolder, definition),supportFileName), true);
                    sourceFile.CopyTo(Path.Combine(this.GetStsAdmFolder(docRootFolder, definition), supportFileName), true);


                }
            }
        }

        private void CreateFile(
            string containingFolder, SafClassDefinition definition, string fileContents, string fileName)
        {
            FileHelper.WriteFile(fileContents, Path.Combine(containingFolder, fileName));
        }

        private void CreateZips(SafClassDefinition definition, string docRootFolder)
        {
            string pathToZipUp = Path.Combine(docRootFolder, definition.FullName);
            using (var zip = new ZipFile())
            {
                zip.AddDirectory(pathToZipUp, definition.FullName);
                //zip.AddSelectedFiles("*.*", pathToZipUp, recurseDirectories);
                zip.Save(Path.Combine(pathToZipUp, Path.Combine(docRootFolder, definition.FullName + ".zip")));
            }
        }

        /// <summary>
        /// Creates the macro templates.
        /// </summary>
        /// <param name="entity">
        /// The entity.
        /// </param>
        /// <param name="context">
        /// </param>
        private void DocumentActions(SafDocInfo entity, SafContext context)
        {
            string actionIndexPage = this.GetTemplatePath(entity, NVelocityTemplateName.ActionIndexPage);
            string codePlexWiki = this.GetTemplatePath(entity, NVelocityTemplateName.CodePlexWiki);
            string classContentPage = this.GetTemplatePath(entity, NVelocityTemplateName.ClassContentsPage);
            string actionMacroTemplateFile = this.GetTemplatePath(entity, NVelocityTemplateName.ActionTemplate);
            string readMeFile = this.GetTemplatePath(entity, NVelocityTemplateName.ReadMe);

            if (Directory.Exists(entity.DocumentationPath))
            {
                FileHelper.DeleteFileSystemInfo(new DirectoryInfo(entity.DocumentationPath));
                //Directory.Delete(entity.DocumentationPath, true);
            }

            Directory.CreateDirectory(entity.DocumentationPath);

            // This produces the actions Index Page - list all actions.
            this.RunNVelocity(
                actionIndexPage,
                context,
                Path.Combine(entity.DocumentationPath, "Default.html"),
                SafDefinitionCache.Instance,
                "SafCache");

            // This produces the actions Index Page - list all actions.
            this.RunNVelocity(
                codePlexWiki,
                context,
                Path.Combine(entity.DocumentationPath, "Codeplex.txt"),
                SafDefinitionCache.Instance,
                "SafCache");

            //erreturn;

            foreach (var pair in SafDefinitionCache.SafClassDefinitions)
            {
                SafClassDefinition safClassDefinition = pair.Value;

                this.EnsureFolders(entity.DocumentationPath, safClassDefinition);

                // this creates the individiual Actions Contents Page
                this.DocumentSpringMacroExample(safClassDefinition, entity.DocumentationPath);

                this.DocumentMSBuildTemplate(entity, context, safClassDefinition, entity.DocumentationPath);

                this.DocumentFeatureXml(entity, context, safClassDefinition, entity.DocumentationPath);

                this.DocumentStsAdm(entity, context, safClassDefinition, entity.DocumentationPath);

                this.DocumentSpringMacroTemplate(safClassDefinition, entity.DocumentationPath);

                this.ProduceReadMeFile(safClassDefinition, context, readMeFile, entity.DocumentationPath);

                this.CopySupportingFiles(entity, context, safClassDefinition, entity.DocumentationPath);

                this.CreateZips(safClassDefinition, entity.DocumentationPath);

                this.DocumentClassDefinition(safClassDefinition, context, classContentPage, entity);
            }

            this.CreateAllSamplesZip(entity.DocumentationPath);

            //this.DocumentClassDefinition(
            //    SafDefinitionCache.SafClassDefinitions,
            //    Path.Combine(entity.DocumentationPath, "Samples"),
            //    context,
            //    readMeFile);

            this.ClearFolder(entity.DocumentationPath);
        }

        private void CreateAllSamplesZip(string docRootFolder)
        {
            using (var zip = new ZipFile())
            {
                foreach (string directory in Directory.GetDirectories(docRootFolder))
                {
                    zip.AddDirectory(directory, directory.Replace(docRootFolder + @"\", ""));    
                }
                zip.Save(Path.Combine(docRootFolder, "AllSamples.zip"));
            }
        }

        private void DocumentClassDefinition(
            SafClassDefinitionCollection definitions, string path, SafContext context, string templateFile)
        {
            // This produces the actions Index Page - list all actions.
            this.RunNVelocity(
                templateFile, context, Path.Combine(path, "ReadMe.txt"), definitions, "SafClassDefinitions");
        }

        /// <summary>
        /// Documents the class definition.
        /// </summary>
        /// <param name="safClassDefinition">The saf class definition.</param>
        /// <param name="context">The context.</param>
        /// <param name="classContentsPage">The class contents page.</param>
        /// <param name="entity">The entity.</param>
        private void DocumentClassDefinition(
            SafClassDefinition safClassDefinition, SafContext context, string classContentsPage, SafDocInfo entity)
        {
            this.RunNVelocity(
                classContentsPage,
                context,
                Path.Combine(entity.DocumentationPath, safClassDefinition.FullName + ".html"),
                safClassDefinition,
                "SafClassDefinition");

            foreach (var pair in safClassDefinition.Properties)
            {
                SafPropertyDefinition propertyDefinition = pair.Value;
                if (propertyDefinition.ClassDefinition != null)
                {
                    this.DocumentClassDefinition(propertyDefinition.ClassDefinition, context, classContentsPage, entity);
                }

                if (propertyDefinition.GenericTypeClassDefinition != null)
                {
                    this.DocumentClassDefinition(
                        propertyDefinition.GenericTypeClassDefinition, context, classContentsPage, entity);
                }

                // if its a Generic Property lets look into the args eg..g List<T, V, G, H>
                // in this case we will be documenting T, V, G, H IF they are classes owned by Saf Attributes.
                if (propertyDefinition.GenericTypeArguments != null)
                {
                    foreach (var argument in propertyDefinition.GenericTypeArguments)
                    {
                        if (argument.Value != null)
                        {
                            this.DocumentClassDefinition(argument.Value, context, classContentsPage, entity);
                        }
                    }
                }
            }
        }

        private void DocumentFeatureXml(
            SafDocInfo entity, SafContext context, SafClassDefinition definition, string docRootFolder)
        {
            string xml = definition.ToFeature(false);
            string productFolderName = definition.ClassName + "Feature";

            //string productFolder = this.ProduceFiles(definition, docRootFolder, productFolderName, xml, this.FeatureXmlfileName);
            string productFolder = this.GetFeatureFolder(docRootFolder, definition);

            this.CreateFile(productFolder, definition, xml, FeatureXmlFileName);

            // This the installation batch file for installing the feature.
            this.RunNVelocity(
                this.GetTemplatePath(entity, NVelocityTemplateName.FeatureInstallTemplate),
                context,
                Path.Combine(productFolder, "InstallFeature.bat"),
                definition,
                "SafClassDefinition");
        }

        /// <summary>
        /// Documents the MS build template.
        /// </summary>
        /// <param name="definition">The definition.</param>
        /// <param name="docRootFolder">The doc root folder.</param>
        /// <returns></returns>
        private void DocumentMSBuildTemplate(SafDocInfo entity, SafContext context, SafClassDefinition definition, string docRootFolder)
        {
            string xml = definition.ToMSBuild(true, false);

            string productFolder = this.GetMSBuildFolder(docRootFolder, definition);

            this.CreateFile(productFolder, definition, xml, String.Format(MSBuildFileName, definition.FullName));

            // This the installation batch file for installing the feature.
            this.RunNVelocity(
                this.GetTemplatePath(entity, NVelocityTemplateName.MSBuildInstallTemplate),
                context,
                Path.Combine(productFolder, "RunMSBuild.bat"),
                definition,
                "SafClassDefinition");
        }

        /// <summary>
        /// Documents the spring macro example.
        /// </summary>
        /// <param name="definition">The definition.</param>
        /// <param name="docRootFolder">The doc root folder.</param>
        /// <returns></returns>
        private void DocumentSpringMacroExample(SafClassDefinition definition, string docRootFolder)
        {
            string xml = definition.ToSpring(false, false);

            string fileName = String.Format(MacroInstanceFileName, definition.FullName);

            this.CreateFile(this.GetFeatureFolder(docRootFolder, definition), definition, xml, fileName);
            this.CreateFile(this.GetMSBuildFolder(docRootFolder, definition), definition, xml, fileName);
            this.CreateFile(this.GetStsAdmFolder(docRootFolder, definition), definition, xml, fileName);
        }

        /// <summary>
        /// Documents the spring macro template.
        /// </summary>
        /// <param name="definition">The definition.</param>
        /// <param name="docRootFolder">The doc root folder.</param>
        /// <returns></returns>
        private void DocumentSpringMacroTemplate(SafClassDefinition definition, string docRootFolder)
        {
            string xml = definition.ToSpring(true, false);

            string fileName = String.Format(MacroTemplateFileName, definition.FullName);

            this.CreateFile(this.GetFeatureFolder(docRootFolder, definition), definition, xml, fileName);
            this.CreateFile(this.GetMSBuildFolder(docRootFolder, definition), definition, xml, fileName);
            this.CreateFile(this.GetStsAdmFolder(docRootFolder, definition), definition, xml, fileName);
        }

        private void DocumentStsAdm(
            SafDocInfo entity, SafContext context, SafClassDefinition definition, string docRootFolder)
        {
            string xml = definition.ToStsAdm(false);

            //string productFolder = this.ProduceFiles(definition, docRootFolder, StsAdmName, xml, RunStsAdmBatFileName);

            this.CreateFile(this.GetStsAdmFolder(docRootFolder, definition), definition, xml, RunStsAdmBatFileName);
        }

        /// <summary>
        /// Setups the doc folder.
        /// </summary>
        /// <param name="directory">The directory.</param>
        private void EnsureFolder(string directory)
        {
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
        }

        private void EnsureFolders(string docRootFolder, SafClassDefinition definition)
        {
            // Create the Feature Folder
            this.EnsureFolder(this.GetFeatureFolder(docRootFolder, definition));

            // Create the MsBulid Folder
            this.EnsureFolder(this.GetMSBuildFolder(docRootFolder, definition));

            // Create the StsAdm Folder
            this.EnsureFolder(this.GetStsAdmFolder(docRootFolder, definition));
        }

        private string GetFeatureFolder(string docRootFolder, SafClassDefinition definition)
        {
            return Path.Combine(docRootFolder, String.Format(FeatureFolder, definition.FullName, definition.ClassName));
        }

        private string GetMSBuildFolder(string docRootFolder, SafClassDefinition definition)
        {
            return Path.Combine(docRootFolder, String.Format(MsBuildFolder, definition.FullName));
        }

        private string GetStsAdmFolder(string docRootFolder, SafClassDefinition definition)
        {
            return Path.Combine(docRootFolder, String.Format(StsAdmFolder, definition.FullName));
        }

        /// <summary>
        /// Gets the template path.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <returns></returns>
        private string GetTemplatePath(SafDocInfo entity, NVelocityTemplateName templateName)
        {
            return Path.Combine(entity.TemplatesPath, templateName + ".vm");
        }

        private void ProduceReadMeFile(
            SafClassDefinition definition, SafContext context, string templateFile, string docRootFolder)
        {
            string path = Path.Combine(docRootFolder, definition.FullName);

            // This produces the actions Index Page - list all actions.
            this.RunNVelocity(
                templateFile, context, Path.Combine(path, "ReadMe.txt"), definition, "SafClassDefinitions");
        }

        /// <summary>
        /// Runs the N velocity.
        /// </summary>
        /// <param name="templateFile">
        /// The template file.
        /// </param>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="fileName">
        /// </param>
        /// <param name="propertyBag">
        /// </param>
        /// <param name="propertyBagName">
        /// </param>
        private void RunNVelocity(
            string templateFile, SafContext context, string fileName, object propertyBag, string propertyBagName)
        {
            var nVelocityInfo = new NVelocityInfo();
            nVelocityInfo.TemplateFile = templateFile;
            nVelocityInfo.OutputFile = fileName;
            nVelocityInfo.IncludeSafProperties = true;
            nVelocityInfo.ReplaceOutputFile = true;
            // refactored from serializable object
            nVelocityInfo.Properties.Add(propertyBagName, propertyBag);

            var action = new NVelocity();
            var entityList = new NVelocityInfo[1];
            entityList[0] = nVelocityInfo;
            action.EntityList = entityList;
            action.Do(context);
        }

        #endregion
    }
}